<?php
namespace WechatPayV3;
require_once('vendor/autoload.php');

use Common\Util\BaseData;
use Common\WechatPayV3\CommandProcess;
use Common\WechatPayV3\LogModel;
use Common\WechatPayV3\WithdrawLog;
use Common\WechatPayV3\WithdrawLogParam;
use Common\WechatPayV3\WxPayException;
use Think\Exception;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
use WechatPayV3\Exceptions\WechatV3PayException;
use function Common\WechatPayV3\arr_get;
use function Common\WechatPayV3\getPaymentSetInfoByToken;

class WechatPayV3 {

    const LOG_TAG = "wxv3_withdraw";

    const WITHDRAW_ACCEPTED = "ACCEPTED";
    const WITHDRAW_PROCESSING = "PROCESSING";
    const WITHDRAW_FINISHED = "FINISHED";
    const WITHDRAW_CLOSED = "CLOSED";

    public function __construct()
    {

    }
    public function getWxPayConfig(){
        return [
            'mechant_id' => arr_get($_ENV,'WX_MECHANT_ID',''),
            'private_key_path' => arr_get($_ENV,'WX_PRIVATE_KEY_PATH'),
            'private_cert_path' => arr_get($_ENV,'WX_PRIVATE_CERT_PATH'),
        ];
    }

    /**
     * @throws Exception
     */
    private function getWxV3PayInstance($token)
    {
        $paymentSet = getPaymentSetInfoByToken($token);
        //检测证书安全性
        $cert = self::getCertFileRealPath($paymentSet);
        //证书路径
        $merchantPrivateCertFilePath = 'file://' . $cert[0];
        $merchantPrivateKeyFilePath = 'file://' . $cert[1];
        $merchantId = $paymentSet['wxmchid'];
        //校验证书路径和商户号，不能为空，否则抛出异常提示
        $platformCertificateFilePath = 'file://' . $cert[2];
        // 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);

        //从文件中获取商户证书序列号
        $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantPrivateCertFilePath);

        // 从本地文件中加载「微信支付平台证书」，用来验证微信支付应答的签名
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid' => $merchantId,
            'serial' => $merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
        ]);

        return $instance;
    }

    //====================微信提现==========================
    public static function getWithdrawStatus(){
        return [self::WITHDRAW_ACCEPTED, self::WITHDRAW_PROCESSING, self::WITHDRAW_FINISHED, self::WITHDRAW_CLOSED];
    }


    /**
     * @param string $order_no
     * @param mixed $cash_info
     * @param $token
     * @return array|null
     */
    public function wxWithdrawOrder($order_no, $cash_info, $token)
    {
        try {
            $this->logModel->addInfoLog($token . "_" . self::LOG_TAG, "wxWithdrawOrder", ["step" => 'start build param', "cash_info" => $cash_info, "token" => $token]);
            $paymentInfo = getPaymentSetInfoByToken($token);
            $cash_info['cash_shijie_jiner'] = intval($cash_info['cash_shijie_jiner'] * 100);
            if ($cash_info['cash_shijie_jiner'] < 30) {
                throw new WechatV3PayException("提现金额不能小于0.3元");
            }
            if ($cash_info['cash_shijie_jiner'] > 50000) {
                throw new WechatV3PayException("提现金额不能超过500元");
            }
            //查询openid
            $cash_info['beizhu'] = isset($cash_info['beizhu']) ? $cash_info['beizhu'] : '';
            $user_info = BaseData::getUserInfoById($cash_info['user_id']);
            $openId = $user_info['user_openid'];
            $transfer_detail_list[0] = array(
                'out_detail_no' => $order_no,
                'transfer_amount' => $cash_info['cash_shijie_jiner'],
                'transfer_remark' => '商家提现' . $cash_info['beizhu'],
                'openid' => $openId
            );
            $parame_data = array(
                'out_batch_no' => $order_no,//付款订单号
                'batch_name' => '商家提现' . $cash_info['user_true_name'],//付款批次名称
                'batch_remark' => '备注',
                'total_num' => 1,//付款总数
                'total_amount' => $cash_info['cash_shijie_jiner'],//付款总金额
                'transfer_detail_list' => $transfer_detail_list//备注
            );
            //基本参数
            $parame_data['appid'] = $paymentInfo['wxappid'];
            //参数排序
            ksort($parame_data, SORT_STRING);
            $parame_data['transfer_detail_list'][0]['openid'] = $openId;
            $parame_data['transfer_detail_list'][0]['transfer_amount'] = $cash_info['cash_shijie_jiner'];
            $parame_data['total_amount'] = $cash_info['cash_shijie_jiner'];

            //校验上面的参数，不能为空
            if (empty($order_no)) throw new WxPayException('商户批次号不能为空！', 401);
            if (empty($cash_info['cash_shijie_jiner'])) throw new WxPayException('付款总金额不能为空！', 401);
            $withdrawResult = $this->withdraw($token, $parame_data);
            $withdrawResult = is_string($withdrawResult) ? json_decode($withdrawResult, true) : $withdrawResult;
            $result = $this->afterWithdraw($cash_info, $withdrawResult);
            $this->logModel->addInfoLog($token . "_" . self::LOG_TAG, __FUNCTION__, ["action" => 'withdraw_result', 'result' => $result]);
            return $result;
        } catch (\Exception $e) {
            $this->logModel->addInfoLog($token . "_" . self::LOG_TAG, __FUNCTION__, ["action" => 'error', 'msg' => $e->getMessage(),'trace' => array_slice($e->getTrace(),0,10)]);
            return array('d_error_code' => $e->getCode(), 'd_error_info' => $e->getMessage());
        }
    }

    private function afterWithdraw($cashInfo, $withdrawResult)
    {
        $logParams = new WithdrawLogParam();
        $withdrawLog = new WithdrawLog();
        $logParams->setShopId($cashInfo["shop_id"]);
        $logParams->setUserId($cashInfo["user_id"]);
        $logParams->setToken($cashInfo["token"]);
        $logParams->setResult($withdrawResult);
        $logParams->setCashId(isset($cashInfo['id'])?$cashInfo['id']:0);

        $withdrawSuccess = $this->judgeWithdrawSuccess($withdrawResult);
        try {
            $logParams->setStatus($withdrawSuccess ? WithdrawLog::STATUS_SUCCESS : WithdrawLog::STATUS_FAIL);
            $logRecord = $withdrawLog->addRecord($logParams);
            (new LogModel())->addInfoLog($cashInfo["token"] . "_" . WechatPayV3::LOG_TAG, "提现日志记录", $logRecord);
        } catch (\Exception $e) {
            (new LogModel())->addInfoLog($cashInfo["token"] . "_" . WechatPayV3::LOG_TAG, "提现日志记录异常", ['msg' => $e->getMessage(),'trace' => array_slice($e->getTrace(),0,5)]);
        }
        return [
            "return_code" => $withdrawSuccess ? "SUCCESS" : "FAIL",
            "result_code" => $withdrawSuccess ? "SUCCESS" : "FAIL",
            "partner_trade_no" => $withdrawResult["batch_id"] ?? '',
            "log_record" => $logRecord
        ];

    }

    private function judgeWithdrawSuccess($withdrawResult)
    {
        $batchStatus = $withdrawResult["batch_status"] ?? '';
        return in_array($batchStatus, WechatPayV3::getWithdrawStatus()) && $batchStatus != WechatPayV3::WITHDRAW_CLOSED;
    }


    /**
     * 微信提现到微信零钱
     * @param $token
     * @param $parame_data
     * @param $requestUrl
     * @return void
     * @throws Exception
     */
    public function withdraw($token, $withdrawData)
    {
        $body = [];
        try {
            $this->logModel->addInfoLog($token."_".self::LOG_TAG, "发送请求接口", $withdrawData);
            $result = self::getWxV3PayInstance($token)
                ->chain('/v3/transfer/batches')
                ->post(['json' => $withdrawData]);
            $body = $result->getBody()->getContents();
            //判断响应码是不是200
            if ($result->getStatusCode() != 200) {
                $this->logModel->addInfoLog($token."_".self::LOG_TAG, "微信提现到微信零钱请求失败", $body);
                return $body;
            }
            $this->logModel->addInfoLog($token."_".self::LOG_TAG, "微信提现到微信零钱请求成功", $body);
        } catch (\Exception $e) {
            $this->logModel->addInfoLog($token . "_" . self::LOG_TAG, "微信提现到微信零钱请求异常", ["msg" => $e->getMessage(), "trace" => $e->getTrace()]);
        }
        return $body;
    }

    public function queryWithdrawStatus(){

    }

    //============================平台证书的获取==============================
    /**
     * 证书处理
     *
     * @param array $paymentInfo
     * @return array
     * @throws Exception
     */
    private function getCertFileRealPath($paymentInfo)
    {
        $certPath = SITE_PATH . substr($paymentInfo['wechat_v3_cret_pem'], 1);
        $keyPath = SITE_PATH . substr($paymentInfo['wechat_v3_key_pem'], 1);
        $platformCertPath = SITE_PATH . substr($paymentInfo['wechat_v3_platform_cert_pem'], 1);
        return array($certPath, $keyPath, $platformCertPath);
    }

    /**
     * 获取下载证书命令
     */
    public function downloadCertificateAndReturnFilePath($token)
    {
        $paymentSet = getPaymentSetInfoByToken($token);
        $merchantId = $paymentSet['wxmchid'];
        $apiV3key = $paymentSet['wechat_v3_api_key'];
        $mchPrivateKeyFilePath = SITE_PATH . substr($paymentSet['wechat_v3_key_pem'], 1);
        // 商户证书序列号
        $mchSerialNo = PemUtil::parseCertificateSerialNo('file://' . SITE_PATH . substr($paymentSet['wechat_v3_cret_pem'], 1));

        $siteDirPrefix = rtrim(SITE_PATH, '/');
        $projectDirPrefix = "/Data/upload/wechat/platform/";
        //路径名 商户号做前缀
        $outputFilePath = $siteDirPrefix . $projectDirPrefix . $merchantId;
        //创建路径
        if (!file_exists($outputFilePath)) {
            mkdir($outputFilePath, 0777, true);
        }
        $outputFilePath = $siteDirPrefix . $projectDirPrefix . $merchantId;
        $this->logModel->addInfoLog($token."_".self::LOG_TAG, "平台证书输出文件路径：outputFilePath", $outputFilePath);
        // 拼接CertificateDownloader 路径
        $certDownloaderPath = SITE_PATH . 'vendor/wechatpay/wechatpay/bin/CertificateDownloader.php';
        $this->logModel->addInfoLog($token."_".self::LOG_TAG, "平台证书输出文件路径：certDownloaderPath", $certDownloaderPath);
        //校验下面的几个参数
        if (empty($apiV3key)) throw new WechatV3PayException('apiV3key不能为空！', 401);
        if (empty($merchantId)) throw new WechatV3PayException('商户号不能为空！', 401);
        if (empty($mchPrivateKeyFilePath)) throw new WechatV3PayException('商户私钥文件路径不能为空！', 401);
        if (empty($mchSerialNo)) throw new WechatV3PayException('商户证书序列号不能为空！', 401);
        if (empty($outputFilePath)) throw new WechatV3PayException('输出文件路径不能为空！', 401);
        $commond = "php -f $certDownloaderPath -- -k ${apiV3key} -m ${merchantId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}";
        $this->logModel->addInfoLog($token."_".self::LOG_TAG, "平台证书commond", $commond);
        $commandProcess = new CommandProcess();
        $shResult = $commandProcess->call($commond);
        // 查找outputFilePath路径下的文件
        $file = $this->findPemPath($shResult, $outputFilePath);
        if (empty($file)) {
            $this->logModel->addInfoLog($token."_".self::LOG_TAG, "证书下载失败:目录下未找到文件", $outputFilePath);
            throw new WechatV3PayException('证书下载失败！', 401);
        }
        //移除开头 $siteDirPrefix
        $filePath = $projectDirPrefix . $merchantId . '/' . $file;
        $this->logModel->addInfoLog($token."_".self::LOG_TAG, "平台证书输出最终文件路径：filePath", $filePath);
        return $filePath;
    }

    public function findPemPath($content, $outputFilePath)
    {
        // 读取$outputFilePath路径下的文件
        $files = scandir($outputFilePath);
        // 找到wecht开头的文件
        foreach ($files as $file) {
            if (strpos($file, 'wechatpay_') !== false) {
                return $file;
            }
        }
        return null;
    }

    //===========================jsapi支付===================================
    public function jsapi(){

    }
}
